import os
import json
import sys
from pathlib import Path
from urllib.parse import urlparse

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings

APP_NAME = "PyQt Browser"
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets"
DEFAULT_SETTINGS = {
    "homepage": (ASSETS_DIR / "startpage.html").as_uri(),
    "search_engine": "https://www.bing.com/search?q={query}",
    "restore_session": True,
    "show_bookmarks_bar": True,
}

BOOKMARKS_FILE = DATA_DIR / "bookmarks.json"
HISTORY_FILE = DATA_DIR / "history.json"
SETTINGS_FILE = DATA_DIR / "settings.json"
SESSION_FILE = DATA_DIR / "session.json"


def ensure_data_dir():
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    if not SETTINGS_FILE.exists():
        SETTINGS_FILE.write_text(json.dumps(DEFAULT_SETTINGS, indent=2, ensure_ascii=False), encoding="utf-8")
    for f in [BOOKMARKS_FILE, HISTORY_FILE, SESSION_FILE]:
        if not f.exists():
            f.write_text("[]" if f.name != "settings.json" else "{}", encoding="utf-8")


def load_json(path: Path, default):
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return default


def save_json(path: Path, data):
    try:
        path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
    except Exception:
        pass


class DownloadManager(QtWidgets.QDockWidget):
    def __init__(self, parent=None):
        super().__init__("下载", parent)
        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea)
        self.list = QtWidgets.QListWidget()
        self.setWidget(self.list)

    def track(self, dl_item):
        text = f"开始下载: {dl_item.url().toString()}"
        row = QtWidgets.QListWidgetItem(text)
        self.list.addItem(row)

        def on_progress():
            received = getattr(dl_item, "receivedBytes", lambda: 0)() if callable(getattr(dl_item, "receivedBytes", None)) else dl_item.receivedBytes()
            total = getattr(dl_item, "totalBytes", lambda: 0)() if callable(getattr(dl_item, "totalBytes", None)) else dl_item.totalBytes()
            percent = (received * 100 / total) if total else 0
            row.setText(f"下载中 {percent:.1f}%: {dl_item.url().toString()}")

        def on_finished():
            state = getattr(dl_item, "state", lambda: None)()
            ok = getattr(dl_item, "isFinished", lambda: True)()
            if ok and state is None or state == getattr(dl_item, "Completed", None):
                row.setText(f"完成: {dl_item.path()}")
            else:
                row.setText(f"失败: {dl_item.url().toString()}")

        # Connect signals defensively for different PyQt versions
        for sig_name in ["downloadProgress", "receivedBytesChanged", "totalBytesChanged"]:
            sig = getattr(dl_item, sig_name, None)
            if callable(getattr(sig, 'connect', None)):
                sig.connect(on_progress)
        if callable(getattr(dl_item, 'finished', None)):
            dl_item.finished.connect(on_finished)
        elif callable(getattr(dl_item, 'isFinishedChanged', None)):
            dl_item.isFinishedChanged.connect(on_finished)


class BrowserPage(QWebEnginePage):
    def __init__(self, profile: QWebEngineProfile, parent=None, on_create_window=None):
        super().__init__(profile, parent)
        self._on_create_window = on_create_window

    def createWindow(self, wintype: QWebEnginePage.WebWindowType):
        # Ask the main window to create a tab and return the page to load into
        if callable(self._on_create_window):
            page = self._on_create_window(wintype)
            if page:
                return page
        # Fallback
        return QWebEnginePage(self.profile())

    def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
        # Print JS console messages to Python console
        print(f"JS[{level}]: {message} ({sourceID}:{lineNumber})")


class BrowserTab(QtWidgets.QWidget):
    titleChanged = QtCore.pyqtSignal(str)
    urlChanged = QtCore.pyqtSignal(QUrl)
    iconChanged = QtCore.pyqtSignal(QtGui.QIcon)

    def __init__(self, profile: QWebEngineProfile, settings: dict, parent=None):
        super().__init__(parent)
        self.settings = settings
        self.view = QWebEngineView(self)
        # on_create_window will be provided by MainWindow when tab is added
        page = BrowserPage(profile, self.view)
        self.view.setPage(page)
        self.view.settings().setAttribute(QWebEngineSettings.FullScreenSupportEnabled, True)
        self.view.settings().setAttribute(QWebEngineSettings.PluginsEnabled, True)
        self.view.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)
        self.view.settings().setAttribute(QWebEngineSettings.LocalStorageEnabled, True)

        # Permissions prompt
        page.featurePermissionRequested.connect(self._on_feature_permission)

        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.view)

        self.view.titleChanged.connect(self.titleChanged)
        self.view.urlChanged.connect(self.urlChanged)
        self.view.iconChanged.connect(self.iconChanged)

    def load(self, url: QUrl):
        self.view.load(url)

    def navigate_to(self, text: str):
        # If it's likely a URL, normalize. Otherwise, search.
        if not text:
            return
        if "://" in text or text.startswith("about:"):
            url = QUrl(text)
        else:
            parsed = urlparse(text)
            if parsed.scheme or "." in parsed.netloc or "." in text:
                url = QUrl.fromUserInput(text)
            else:
                url = QUrl(self.settings.get("search_engine", DEFAULT_SETTINGS["search_engine"]).format(query=QtCore.QUrl.toPercentEncoding(text).data().decode()))
        self.view.load(url)

    def _on_feature_permission(self, securityOrigin, feature):
        features = {
            QWebEnginePage.Geolocation: "地理位置",
            QWebEnginePage.MediaAudioCapture: "麦克风",
            QWebEnginePage.MediaVideoCapture: "摄像头",
            QWebEnginePage.Notifications: "通知",
            QWebEnginePage.MouseLock: "鼠标锁定",
            QWebEnginePage.DesktopVideoCapture: "屏幕共享",
            QWebEnginePage.DesktopAudioVideoCapture: "屏幕与音频共享",
        }
        name = features.get(feature, str(int(feature)))
        origin = securityOrigin.toString()
        ret = QtWidgets.QMessageBox.question(self, "站点权限", f"允许 {origin} 访问 {name} 吗？")
        if ret == QtWidgets.QMessageBox.Yes:
            self.view.page().setFeaturePermission(securityOrigin, feature, QWebEnginePage.PermissionGrantedByUser)
        else:
            self.view.page().setFeaturePermission(securityOrigin, feature, QWebEnginePage.PermissionDeniedByUser)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, incognito=False):
        super().__init__()
        ensure_data_dir()
        self.app_settings = load_json(SETTINGS_FILE, DEFAULT_SETTINGS)

        self.setWindowTitle(APP_NAME)
        self.setWindowIcon(QIcon.fromTheme("applications-internet"))
        self.resize(1200, 800)

        # Profile
        if incognito:
            self.profile = QWebEngineProfile()
            self.profile.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
            self.profile.setCachePath("")
            self.profile.setPersistentStoragePath("")
        else:
            self.profile = QWebEngineProfile.defaultProfile()
            cache = str(DATA_DIR / "cache")
            storage = str(DATA_DIR / "storage")
            self.profile.setCachePath(cache)
            self.profile.setPersistentStoragePath(storage)

        # Downloads
        self.downloads = DownloadManager(self)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.downloads)
        self.downloads.hide()
        self.profile.downloadRequested.connect(self.handle_download)

        # Central: tabs + toolbar + address
        self.tabs = QtWidgets.QTabWidget()
        self.tabs.setTabsClosable(True)
        self.tabs.setDocumentMode(True)
        self.tabs.setMovable(True)
        self.tabs.tabCloseRequested.connect(self.close_tab)
        self.tabs.currentChanged.connect(self.current_tab_changed)
        # Tab context menu
        self.tabs.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tabs.customContextMenuRequested.connect(self._on_tabbar_context_menu)

        self.nav_toolbar = QtWidgets.QToolBar("导航")
        self.nav_toolbar.setMovable(False)
        self.addToolBar(Qt.TopToolBarArea, self.nav_toolbar)

        self.action_back = QtWidgets.QAction("←", self, triggered=lambda: self.current_view().back())
        self.action_forward = QtWidgets.QAction("→", self, triggered=lambda: self.current_view().forward())
        self.action_reload = QtWidgets.QAction("刷新", self, triggered=lambda: self.current_view().reload())
        self.action_stop = QtWidgets.QAction("停止", self, triggered=lambda: self.current_view().stop())
        self.action_home = QtWidgets.QAction("主页", self, triggered=self.go_home)
        self.action_new_tab = QtWidgets.QAction("新标签", self, shortcut="Ctrl+T", triggered=self.add_tab)
        self.action_incognito = QtWidgets.QAction("隐身窗口", self, shortcut="Ctrl+Shift+N", triggered=self.open_incognito)
        self.action_zoom_in = QtWidgets.QAction("放大", self, shortcut="Ctrl+=", triggered=lambda: self.current_view().setZoomFactor(self.current_view().zoomFactor()+0.1))
        self.action_zoom_out = QtWidgets.QAction("缩小", self, shortcut="Ctrl-", triggered=lambda: self.current_view().setZoomFactor(self.current_view().zoomFactor()-0.1))
        self.action_zoom_reset = QtWidgets.QAction("100%", self, shortcut="Ctrl+0", triggered=lambda: self.current_view().setZoomFactor(1.0))
        self.action_print_pdf = QtWidgets.QAction("另存为PDF", self, triggered=self.print_to_pdf)
        self.action_find = QtWidgets.QAction("查找", self, shortcut="Ctrl+F", triggered=self.find_in_page)
        self.action_devtools = QtWidgets.QAction("开发者工具", self, shortcut="F12", triggered=self.open_devtools)
        self.action_mute = QtWidgets.QAction("静音/取消静音", self, shortcut="Ctrl+M", triggered=self.toggle_mute)
        self.action_duplicate_tab = QtWidgets.QAction("复制标签页", self, triggered=self.duplicate_current_tab)
        self.action_close_tab = QtWidgets.QAction("关闭标签页", self, shortcut="Ctrl+W", triggered=lambda: self.close_tab(self.tabs.currentIndex()))
        self.action_reopen_closed = QtWidgets.QAction("重新打开已关闭标签页", self, shortcut="Ctrl+Shift+T", triggered=self.reopen_closed_tab)
        self.action_new_window = QtWidgets.QAction("新窗口", self, shortcut="Ctrl+N", triggered=self.open_new_window)
        self.action_focus_location = QtWidgets.QAction("焦点到地址栏", self, shortcut="Ctrl+L", triggered=lambda: self.address.setFocus(Qt.ShortcutFocusReason))
        self.action_view_source = QtWidgets.QAction("查看源代码", self, shortcut="Ctrl+U", triggered=self.view_page_source)

        for a in [self.action_back, self.action_forward, self.action_reload, self.action_stop, self.action_home]:
            self.nav_toolbar.addAction(a)

        self.address = QtWidgets.QLineEdit(self)
        self.address.setClearButtonEnabled(True)
        self.address.returnPressed.connect(self.on_address_enter)
        address_widget = QtWidgets.QWidget(self)
        hl = QtWidgets.QHBoxLayout(address_widget)
        hl.setContentsMargins(8, 4, 8, 4)
        # Make the address bar expand to take more space
        self.address.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
        address_widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
        address_widget.setMinimumWidth(700)
        hl.addWidget(self.address)
        self.nav_toolbar.addWidget(address_widget)

        self.nav_toolbar.addAction(self.action_new_tab)
        self.nav_toolbar.addSeparator()
        self.nav_toolbar.addAction(self.action_zoom_out)
        self.nav_toolbar.addAction(self.action_zoom_reset)
        self.nav_toolbar.addAction(self.action_zoom_in)
        self.nav_toolbar.addSeparator()
        self.nav_toolbar.addAction(self.action_print_pdf)
        self.nav_toolbar.addAction(self.action_find)
        self.nav_toolbar.addSeparator()
        self.nav_toolbar.addAction(self.action_incognito)
        self.nav_toolbar.addAction(self.action_devtools)
        self.nav_toolbar.addAction(self.action_mute)
        self.addAction(self.action_close_tab)
        self.addAction(self.action_reopen_closed)
        self.addAction(self.action_new_window)
        self.addAction(self.action_focus_location)
        self.addAction(self.action_view_source)
        # Shorter tab titles: elide long text and avoid expanding
        try:
            self.tabs.tabBar().setElideMode(Qt.ElideRight)
            self.tabs.tabBar().setExpanding(False)
        except Exception:
            pass

        # Bookmarks bar
        self.bookmarks_bar = QtWidgets.QToolBar("书签栏")
        self.bookmarks_bar.setMovable(False)
        self.addToolBar(Qt.TopToolBarArea, self.bookmarks_bar)
        self.bookmarks_bar.setVisible(bool(self.app_settings.get("show_bookmarks_bar", True)))
        self.reload_bookmarks_bar()

        self.setCentralWidget(self.tabs)

        # Menu
        m = self.menuBar()
        file_menu = m.addMenu("文件")
        file_menu.addAction(self.action_new_tab)
        file_menu.addAction(self.action_new_window)
        file_menu.addAction(self.action_print_pdf)
        file_menu.addSeparator()
        exit_action = QtWidgets.QAction("退出", self, triggered=self.close)
        file_menu.addAction(exit_action)

        view_menu = m.addMenu("视图")
        view_menu.addAction(self.action_zoom_in)
        view_menu.addAction(self.action_zoom_out)
        view_menu.addAction(self.action_zoom_reset)
        toggle_bm = QtWidgets.QAction("切换书签栏", self, triggered=self.toggle_bookmarks_bar)
        view_menu.addAction(toggle_bm)
        # Theme toggle
        self.action_toggle_theme = QtWidgets.QAction("切换现代主题", self, checkable=True)
        self.action_toggle_theme.setChecked(bool(self.app_settings.get("modern_theme", True)))
        self.action_toggle_theme.triggered.connect(self.toggle_theme)
        view_menu.addAction(self.action_toggle_theme)

        tools_menu = m.addMenu("工具")
        tools_menu.addAction(self.action_devtools)
        clear_data = QtWidgets.QAction("清除浏览数据…", self, triggered=self.clear_browsing_data)
        tools_menu.addAction(clear_data)
        tools_menu.addAction(self.action_duplicate_tab)
        tools_menu.addAction(self.action_view_source)

        library_menu = m.addMenu("收藏与历史")
        bm_mgr = QtWidgets.QAction("书签管理器…", self, triggered=self.open_bookmarks_manager)
        hist_mgr = QtWidgets.QAction("历史记录…", self, triggered=self.open_history_manager)
        library_menu.addAction(bm_mgr)
        library_menu.addAction(hist_mgr)

        help_menu = m.addMenu("帮助")
        about_action = QtWidgets.QAction("关于", self, triggered=self.show_about)
        help_menu.addAction(about_action)

        # Session restore or home
        session = load_json(SESSION_FILE, []) if self.app_settings.get("restore_session", True) else []
        if session:
            for url in session:
                self.add_tab(QUrl(url))
        else:
            self.add_tab(QUrl(self.app_settings.get("homepage", DEFAULT_SETTINGS["homepage"])))

        # Address suggestions from history + bookmarks
        self._refresh_completer()

        # Page context menu (basic)
        self.setContextMenuPolicy(Qt.NoContextMenu)
        # Track closed tabs
        self.closed_tabs = []

        # Apply theme after widgets created
        self.apply_theme()

    # Helpers
    def current_tab(self) -> BrowserTab:
        return self.tabs.currentWidget()

    def current_view(self) -> QWebEngineView:
        return self.current_tab().view

    def on_address_enter(self):
        text = self.address.text().strip()
        self.current_tab().navigate_to(text)

    def current_tab_changed(self, index: int):
        tab = self.current_tab()
        if not tab:
            return
        self.address.setText(tab.view.url().toString())
        self.setWindowTitle(f"{tab.view.title()} - {APP_NAME}")

    def add_tab(self, url: QUrl | None = None):
        tab = BrowserTab(self.profile, self.app_settings, self)
        # Wire createWindow to open in a new tab and return the page for loading
        def _on_create_window(wintype):
            # If popup asks for new window, we still open as tab
            new_tab = BrowserTab(self.profile, self.app_settings, self)
            idx = self.tabs.addTab(new_tab, "新标签页")
            self.tabs.setCurrentIndex(idx)
            return new_tab.view.page()
        tab.view.page()._on_create_window = _on_create_window
        i = self.tabs.addTab(tab, "新标签页")
        self.tabs.setCurrentIndex(i)
        tab.titleChanged.connect(lambda title, t=tab: self.update_tab_title(t, title))
        tab.urlChanged.connect(lambda qurl, t=tab: self.on_tab_url_changed(t, qurl))
        tab.iconChanged.connect(lambda icon, t=tab: self.update_tab_icon(t, icon))
        if url is None:
            url = QUrl(self.app_settings.get("homepage", DEFAULT_SETTINGS["homepage"]))
        tab.load(url)

    def update_tab_title(self, tab: BrowserTab, title: str):
        idx = self.tabs.indexOf(tab)
        if idx >= 0:
            self.tabs.setTabText(idx, title[:24] + ("…" if len(title) > 24 else ""))
        if tab == self.current_tab():
            self.setWindowTitle(f"{title} - {APP_NAME}")

    def update_tab_icon(self, tab: BrowserTab, icon: QtGui.QIcon):
        idx = self.tabs.indexOf(tab)
        if idx >= 0:
            self.tabs.setTabIcon(idx, icon)

    def on_tab_url_changed(self, tab: BrowserTab, url: QUrl):
        if tab == self.current_tab():
            self.address.setText(url.toString())
        # append to history
        history = load_json(HISTORY_FILE, [])
        history.append({"url": url.toString(), "ts": int(QtCore.QDateTime.currentSecsSinceEpoch())})
        save_json(HISTORY_FILE, history[-1000:])
        self._refresh_completer()

    def close_tab(self, index: int):
        w = self.tabs.widget(index)
        if w:
            # save to closed stack
            try:
                url = w.view.url().toString()
                if url:
                    self.closed_tabs.append(url)
                    self.closed_tabs = self.closed_tabs[-20:]
            except Exception:
                pass
            w.deleteLater()
        self.tabs.removeTab(index)
        if self.tabs.count() == 0:
            self.add_tab()

    def go_home(self):
        self.current_tab().load(QUrl(self.app_settings.get("homepage", DEFAULT_SETTINGS["homepage"])))

    def toggle_bookmarks_bar(self):
        self.bookmarks_bar.setVisible(not self.bookmarks_bar.isVisible())
        self.app_settings["show_bookmarks_bar"] = self.bookmarks_bar.isVisible()
        save_json(SETTINGS_FILE, self.app_settings)

    def toggle_theme(self, checked: bool):
        self.app_settings["modern_theme"] = bool(checked)
        save_json(SETTINGS_FILE, self.app_settings)
        self.apply_theme()

    def apply_theme(self):
        # Load optional stylesheet for a more modern look
        use_modern = bool(self.app_settings.get("modern_theme", True))
        if use_modern:
            css_path = ASSETS_DIR / "themes" / "modern.css"
            # Keep Qt widgets styled: we only style our bars via setStyleSheet snippets
            # Read variables to color our widgets
            try:
                # Minimal colors to avoid parsing CSS: use defaults approximations
                bg = "#151821"; fg = "#e5e7eb"; border = "#242836"
                if os.path.exists(css_path):
                    # Not parsing; just use palette approximations
                    pass
                bar_style = f"background:{bg}; border: 0; border-bottom:1px solid {border}; color:{fg};"
                addr_style = f"QLineEdit{{background:#0b0e14; color:{fg}; border:1px solid {border}; border-radius:8px; padding:6px 8px;}}"
                self.nav_toolbar.setStyleSheet(bar_style)
                self.bookmarks_bar.setStyleSheet(bar_style)
                self.address.setStyleSheet(addr_style)
                # Tab bar via style on QTabWidget::pane/tab-bar
                self.tabs.setStyleSheet(
                    f"QTabWidget::pane{{border-top:1px solid {border};}}"
                    f"QTabBar::tab{{background:{bg}; color:{fg}; padding:4px 8px; max-width:160px; border:1px solid {border}; border-bottom: none; border-top-left-radius:6px; border-top-right-radius:6px;}}"
                    f"QTabBar::tab:selected{{background:#1c2230;}}"
                )
            except Exception:
                pass
        else:
            # Reset to default look
            self.nav_toolbar.setStyleSheet("")
            self.bookmarks_bar.setStyleSheet("")
            self.address.setStyleSheet("")
            self.tabs.setStyleSheet("")
            # Keep shorter tabs in default theme too
            try:
                self.tabs.tabBar().setElideMode(Qt.ElideRight)
                self.tabs.tabBar().setExpanding(False)
            except Exception:
                pass

    def show_about(self):
        QtWidgets.QMessageBox.information(self, "关于", f"{APP_NAME}\n使用 PyQt5 + Qt WebEngine 构建")

    def print_to_pdf(self):
        path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "另存为 PDF", str(DATA_DIR / "page.pdf"), "PDF Files (*.pdf)")
        if not path:
            return
        self.current_view().page().printToPdf(path)

    def view_page_source(self):
        page = self.current_view().page()
        def _show_source(html: str):
            prefix = "<html><head><meta charset='utf-8'><title>源代码</title></head><body><pre>"
            suffix = "</pre></body></html>"
            if hasattr(QtCore.Qt, 'escape'):
                content = QtCore.Qt.escape(html)
            else:
                content = html.replace("&","&amp;").replace("<","&lt;")
            src_html = prefix + content + suffix
            new_tab = BrowserTab(self.profile, self.app_settings, self)
            idx = self.tabs.addTab(new_tab, "源代码")
            self.tabs.setCurrentIndex(idx)
            new_tab.view.setHtml(src_html)
        page.toHtml(_show_source)

    def find_in_page(self):
        term, ok = QtWidgets.QInputDialog.getText(self, "查找", "关键字：")
        if ok and term:
            self.current_view().findText("")
            self.current_view().findText(term)

    def handle_download(self, item):
        # Ask where to save if no default path
        filename = item.path()
        if not filename:
            suggested = Path(item.url().path()).name or "download.bin"
            path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "保存文件", str(DATA_DIR / suggested))
            if not path:
                item.cancel()
                return
            item.setPath(path)
        item.accept()

        # Track in downloads panel
        self.downloads.track(item)
        self.downloads.show()
        # Double click to open file/folder
        if not hasattr(self, '_dl_list_hooked'):
            self.downloads.list.itemDoubleClicked.connect(self._open_download_item)
            self._dl_list_hooked = True

    def _open_download_item(self, list_item: QtWidgets.QListWidgetItem):
        text = list_item.text()
        if text.startswith("完成: "):
            path = text.replace("完成: ", "", 1)
            if os.path.exists(path):
                QtGui.QDesktopServices.openUrl(QUrl.fromLocalFile(path))

    def reload_bookmarks_bar(self):
        self.bookmarks_bar.clear()
        bookmarks = load_json(BOOKMARKS_FILE, [])
        for bm in bookmarks:
            act = QtWidgets.QAction(bm.get("title", bm.get("url", "书签")), self)
            act.triggered.connect(lambda checked=False, u=bm.get("url", ""): self.current_tab().load(QUrl(u)))
            self.bookmarks_bar.addAction(act)
        self.bookmarks_bar.addSeparator()
        star = QtWidgets.QAction("★ 添加书签", self, triggered=self.toggle_bookmark_current)
        self.bookmarks_bar.addAction(star)

    def toggle_bookmark_current(self):
        url = self.current_view().url().toString()
        title = self.current_view().title() or url
        bookmarks = load_json(BOOKMARKS_FILE, [])
        exists = next((b for b in bookmarks if b.get("url") == url), None)
        if exists:
            bookmarks = [b for b in bookmarks if b.get("url") != url]
        else:
            bookmarks.append({"title": title, "url": url})
        save_json(BOOKMARKS_FILE, bookmarks)
        self.reload_bookmarks_bar()

    def save_session(self):
        if not self.app_settings.get("restore_session", True):
            return
        urls = []
        for i in range(self.tabs.count()):
            w = self.tabs.widget(i)
            if isinstance(w, BrowserTab):
                urls.append(w.view.url().toString())
        save_json(SESSION_FILE, urls)

    def open_incognito(self):
        # Start a new incognito window
        self.child = MainWindow(incognito=True)
        self.child.show()

    def closeEvent(self, e: QtGui.QCloseEvent) -> None:
        self.save_session()
        return super().closeEvent(e)

    # New features
    def reopen_closed_tab(self):
        if self.closed_tabs:
            url = self.closed_tabs.pop()
            self.add_tab(QUrl(url))

    def open_devtools(self):
        page = self.current_view().page()
        # Try native devtools page if available
        try:
            if not hasattr(self, "_devtools"):
                self._devtools = QtWidgets.QMainWindow(self)
                self._devtools.setWindowTitle("DevTools")
                self._devtools.resize(900, 700)
                self._devtools_view = QWebEngineView(self._devtools)
                self._devtools.setCentralWidget(self._devtools_view)
                dev_page = QWebEnginePage(self.profile, self._devtools_view)
                self._devtools_view.setPage(dev_page)
                if hasattr(page, 'setDevToolsPage'):
                    page.setDevToolsPage(dev_page)
            self._devtools.show()
            self._devtools.raise_()
            self._devtools.activateWindow()
        except Exception:
            # Fallback to remote debugging
            os.environ.setdefault("QTWEBENGINE_REMOTE_DEBUGGING", "9222")
            self.add_tab(QUrl("http://127.0.0.1:9222"))

    def toggle_mute(self):
        p = self.current_view().page()
        p.setAudioMuted(not p.isAudioMuted())

    def duplicate_current_tab(self):
        url = self.current_view().url()
        self.add_tab(url)

    def clear_browsing_data(self):
        btn = QtWidgets.QMessageBox.question(self, "清除数据", "清除缓存、Cookies、存储和历史记录？此操作不可撤销。")
        if btn != QtWidgets.QMessageBox.Yes:
            return
        try:
            # Clear cache
            self.profile.clearHttpCache()
            # Clear cookies
            store = self.profile.cookieStore()
            def delete_cookie(c):
                store.deleteCookie(c)
            store.cookieAdded.connect(delete_cookie)
            # Clear persistent storage by removing the directory
            storage = self.profile.persistentStoragePath()
            if storage and os.path.isdir(storage):
                import shutil
                shutil.rmtree(storage, ignore_errors=True)
                os.makedirs(storage, exist_ok=True)
        except Exception as e:
            QtWidgets.QMessageBox.warning(self, "清除失败", str(e))
        # Clear local history files
        save_json(HISTORY_FILE, [])
        save_json(SESSION_FILE, [])
        QtWidgets.QMessageBox.information(self, "完成", "已清除浏览数据。重启后生效更完全。")

    def _refresh_completer(self):
        # Build completion list from bookmarks + recent history
        bookmarks = load_json(BOOKMARKS_FILE, [])
        history = load_json(HISTORY_FILE, [])[-200:]
        items = list({*(b.get('url','') for b in bookmarks), *(h.get('url','') for h in history)})
        model = QtCore.QStringListModel([u for u in items if u])
        comp = QtWidgets.QCompleter(model, self)
        comp.setCaseSensitivity(Qt.CaseInsensitive)
        comp.setFilterMode(Qt.MatchContains)
        self.address.setCompleter(comp)

    # UI helpers
    def _on_tabbar_context_menu(self, pos):
        index = self.tabs.tabBar().tabAt(pos)
        if index < 0:
            return
        m = QtWidgets.QMenu(self)
        act_close = m.addAction("关闭")
        act_close_others = m.addAction("关闭其它")
        act_close_right = m.addAction("关闭右侧标签")
        act_reload = m.addAction("刷新")
        act_dup = m.addAction("复制")
        chosen = m.exec_(self.tabs.tabBar().mapToGlobal(pos))
        if chosen == act_close:
            self.close_tab(index)
        elif chosen == act_close_others:
            for i in reversed(range(self.tabs.count())):
                if i != index:
                    self.close_tab(i)
        elif chosen == act_close_right:
            for i in reversed(range(index+1, self.tabs.count())):
                self.close_tab(i)
        elif chosen == act_reload:
            w = self.tabs.widget(index)
            if isinstance(w, BrowserTab):
                w.view.reload()
        elif chosen == act_dup:
            w = self.tabs.widget(index)
            if isinstance(w, BrowserTab):
                self.add_tab(w.view.url())

    # Managers
    def open_history_manager(self):
        dlg = QtWidgets.QDialog(self)
        dlg.setWindowTitle("历史记录")
        dlg.resize(700, 500)
        v = QtWidgets.QVBoxLayout(dlg)
        listw = QtWidgets.QListWidget(dlg)
        v.addWidget(listw)
        btns = QtWidgets.QHBoxLayout()
        open_btn = QtWidgets.QPushButton("打开")
        clear_btn = QtWidgets.QPushButton("清空")
        close_btn = QtWidgets.QPushButton("关闭")
        btns.addWidget(open_btn)
        btns.addWidget(clear_btn)
        btns.addStretch(1)
        btns.addWidget(close_btn)
        v.addLayout(btns)
        hist = load_json(HISTORY_FILE, [])
        for h in reversed(hist):
            listw.addItem(h.get('url',''))
        def open_selected():
            it = listw.currentItem()
            if it:
                self.add_tab(QUrl(it.text()))
        def clear_hist():
            save_json(HISTORY_FILE, [])
            listw.clear()
        open_btn.clicked.connect(open_selected)
        clear_btn.clicked.connect(clear_hist)
        close_btn.clicked.connect(dlg.accept)
        listw.itemDoubleClicked.connect(lambda it: (self.add_tab(QUrl(it.text())), dlg.accept()))
        dlg.exec_()

    def open_bookmarks_manager(self):
        dlg = QtWidgets.QDialog(self)
        dlg.setWindowTitle("书签管理器")
        dlg.resize(700, 500)
        v = QtWidgets.QVBoxLayout(dlg)
        listw = QtWidgets.QListWidget(dlg)
        v.addWidget(listw)
        input_title = QtWidgets.QLineEdit()
        input_url = QtWidgets.QLineEdit()
        input_title.setPlaceholderText("标题")
        input_url.setPlaceholderText("URL")
        form = QtWidgets.QFormLayout()
        form.addRow("标题", input_title)
        form.addRow("URL", input_url)
        v.addLayout(form)
        btns = QtWidgets.QHBoxLayout()
        add_btn = QtWidgets.QPushButton("添加/更新")
        del_btn = QtWidgets.QPushButton("删除")
        open_btn = QtWidgets.QPushButton("打开")
        export_btn = QtWidgets.QPushButton("导出(JSON)")
        import_btn = QtWidgets.QPushButton("导入(JSON)")
        close_btn = QtWidgets.QPushButton("关闭")
        for b in [add_btn, del_btn, open_btn, export_btn, import_btn]:
            btns.addWidget(b)
        btns.addStretch(1)
        btns.addWidget(close_btn)
        v.addLayout(btns)
        def reload_list():
            listw.clear()
            for b in load_json(BOOKMARKS_FILE, []):
                listw.addItem(f"{b.get('title','')}|{b.get('url','')}")
        def on_select(it):
            if not it: return
            parts = it.text().split('|', 1)
            input_title.setText(parts[0])
            input_url.setText(parts[1] if len(parts)>1 else '')
        def add_or_update():
            title = input_title.text().strip()
            url = input_url.text().strip()
            if not url:
                return
            bms = load_json(BOOKMARKS_FILE, [])
            exists = next((b for b in bms if b.get('url')==url), None)
            if exists:
                exists['title'] = title or exists.get('title','')
            else:
                bms.append({'title': title or url, 'url': url})
            save_json(BOOKMARKS_FILE, bms)
            self.reload_bookmarks_bar()
            reload_list()
        def delete_sel():
            it = listw.currentItem()
            if not it:
                return
            _, url = (it.text().split('|',1)+[''])[:2]
            bms = [b for b in load_json(BOOKMARKS_FILE, []) if b.get('url')!=url]
            save_json(BOOKMARKS_FILE, bms)
            self.reload_bookmarks_bar()
            reload_list()
        def open_sel():
            it = listw.currentItem()
            if it:
                _, url = (it.text().split('|',1)+[''])[:2]
                self.add_tab(QUrl(url))
        def export_json():
            path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "导出书签", str(DATA_DIR / 'bookmarks.json'), "JSON Files (*.json)")
            if not path: return
            import shutil
            shutil.copyfile(str(BOOKMARKS_FILE), path)
        def import_json():
            path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "导入书签", str(DATA_DIR), "JSON Files (*.json)")
            if not path: return
            try:
                data = json.loads(Path(path).read_text(encoding='utf-8'))
                if isinstance(data, list):
                    save_json(BOOKMARKS_FILE, data)
                    self.reload_bookmarks_bar()
                    reload_list()
            except Exception as e:
                QtWidgets.QMessageBox.warning(self, "导入失败", str(e))
        reload_list()
        listw.currentItemChanged.connect(lambda it, prev: on_select(it))
        listw.itemDoubleClicked.connect(lambda it: (on_select(it), open_sel()))
        add_btn.clicked.connect(add_or_update)
        del_btn.clicked.connect(delete_sel)
        open_btn.clicked.connect(open_sel)
        export_btn.clicked.connect(export_json)
        import_btn.clicked.connect(import_json)
        close_btn.clicked.connect(dlg.accept)
        dlg.exec_()

    def open_new_window(self):
        w = MainWindow(incognito=False)
        w.show()
        # Keep a reference to avoid GC
        if not hasattr(self, '_child_windows'):
            self._child_windows = []
        self._child_windows.append(w)


def main():
    # Optional GPU/remote debugging flags
    os.environ.setdefault("QTWEBENGINE_CHROMIUM_FLAGS", "--enable-features=NetworkServiceInProcess")

    app = QtWidgets.QApplication(sys.argv)
    app.setApplicationName(APP_NAME)

    win = MainWindow()
    win.show()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
